use std::sync::LazyLock;

use hashbrown::HashMap;
use parking_lot::RwLock;

use super::HttpProxy;
use crate::error::*;

type HttpVariableGetHandle = dyn Fn(&mut HttpProxy) -> Option<&str> + Sync;
type HttpVariableSetHandle =
    dyn Fn(&mut HttpProxy, Option<String>) -> Result<()> + Sync;

#[allow(dead_code)]
#[derive(Clone)]
pub struct HttpVariable {
    name: &'static str,
    index: usize,
    def_data: Option<String>,
    data: Option<String>,
    get_handle: Option<&'static HttpVariableGetHandle>,
    set_handle: Option<&'static HttpVariableSetHandle>,
    immutable: bool,
}

#[allow(dead_code)]
pub struct HttpVariableTable {
    vec: Vec<HttpVariable>,
    hash: HashMap<&'static str, usize>,
}

#[allow(dead_code)]
impl HttpVariableTable {
    #[inline]
    pub fn new() -> Self {
        HttpVariableTable {
            vec: Vec::with_capacity(8),
            hash: HashMap::with_capacity(8),
        }
    }

    #[inline]
    pub fn copy(&self) -> Self {
        HttpVariableTable {
            vec: self.vec.clone(),
            hash: self.hash.clone(),
        }
    }

    pub fn insert(&mut self, mut v: HttpVariable) -> Result<usize> {
        let name = v.name;

        if self.hash.get(name).is_some() {
            return Error::e_explain(
                ErrorType::Conflict,
                format!("conflicting vaiable name: {}", name),
            );
        }

        let index = self.vec.len();
        v.index = index;

        self.vec.push(v);
        self.hash.insert(name, index);

        Ok(index)
    }

    #[inline]
    pub fn get_by_index(&self, index: usize) -> Option<&HttpVariable> {
        if index > self.vec.len() - 1 {
            return None;
        }

        Some(&self.vec[index])
    }

    #[inline]
    pub fn get_by_name(&self, name: &str) -> Option<&HttpVariable> {
        if let Some(index) = self.hash.get(name) {
            return self.get_by_index(*index);
        }

        None
    }

    #[inline]
    pub fn get_by_index_mut(
        &mut self,
        index: usize,
    ) -> Option<&mut HttpVariable> {
        if index > self.vec.len() - 1 {
            return None;
        }

        Some(&mut self.vec[index])
    }

    #[inline]
    pub fn get_by_name_mut(&mut self, name: &str) -> Option<&mut HttpVariable> {
        if let Some(index) = self.hash.get(name) {
            return self.get_by_index_mut(*index);
        }

        None
    }

    #[inline]
    pub fn get_index_by_name(&self, name: &str) -> Option<usize> {
        if let Some(index) = self.hash.get(name) {
            return Some(*index);
        }

        None
    }
}

#[allow(dead_code)]
impl HttpProxy {
    #[inline]
    pub fn get_var_by_name(&mut self, name: &str) -> Option<&str> {
        if let Some(index) = self.variables.get_index_by_name(name) {
            return self.get_var_by_index(index);
        }

        None
    }

    pub fn get_var_by_index(&mut self, index: usize) -> Option<&str> {
        let var = match self.variables.get_by_index(index) {
            Some(var) => var,
            None => {
                return None;
            }
        };

        if var.data.is_some() {
            // 因Rust借用规则冲突, 需重新获取一次var.
            let var = self.variables.get_by_index(index).unwrap();
            return var.data.as_deref();
        }

        let get_handle = var.get_handle;
        if let Some(handle) = get_handle {
            return handle(self);
        }

        None
    }

    pub fn set_var_by_name(
        &mut self,
        name: &str,
        value: Option<String>,
    ) -> Result<()> {
        if let Some(index) = self.variables.get_index_by_name(name) {
            return self.set_var_by_index(index, value);
        }

        Error::e_explain(
            ErrorType::NotFoundError,
            format!("variable {} not found", name),
        )
    }

    pub fn set_var_by_index(
        &mut self,
        index: usize,
        value: Option<String>,
    ) -> Result<()> {
        let var = self.variables.get_by_index_mut(index).if_none(
            ErrorType::NotFoundError,
            format!("variable index {} not found", index),
        )?;

        if var.immutable {
            return Error::e_explain(
                ErrorType::ImmutableVar,
                format!("variable {} immutable", var.name),
            );
        }

        let set_handle = var.set_handle;
        if let Some(handle) = set_handle {
            return handle(self, value);
        }

        var.data = value;
        Ok(())
    }
}

#[allow(dead_code)]
pub static GLOBAL_HTTP_VARIABLE_TABLE: LazyLock<RwLock<HttpVariableTable>> =
    LazyLock::new(|| RwLock::new(HttpVariableTable::new()));

#[cfg(test)]
mod tests {
    use crate::{
        http::{
            base::conn::HttpConn,
            proxy::{session::ServerSession, HttpProxy},
        },
        stream::create_mock_stream,
    };

    use super::{HttpVariable, GLOBAL_HTTP_VARIABLE_TABLE};

    fn v1_get(_proxy: &mut HttpProxy) -> Option<&str> {
        Some("v1")
    }

    fn v2_get(_proxy: &mut HttpProxy) -> Option<&str> {
        Some("v2")
    }

    #[test]
    fn test_variable() {
        {
            let mut w_tab = GLOBAL_HTTP_VARIABLE_TABLE.write();

            let v1 = HttpVariable {
                name: "v1",
                index: 0,
                def_data: None,
                data: None,
                get_handle: Some(&v1_get),
                set_handle: None,
                immutable: true,
            };
            w_tab.insert(v1).unwrap();

            let v2 = HttpVariable {
                name: "v2",
                index: 0,
                def_data: None,
                data: None,
                get_handle: Some(&v2_get),
                set_handle: None,
                immutable: true,
            };
            w_tab.insert(v2).unwrap();
        }

        let (c_stream, _server_stream) = create_mock_stream();
        let hc = Box::new(HttpConn::new_with_stream(c_stream, None));
        let server_session = ServerSession::new_http1(hc);
        let mut proxy = HttpProxy::new(server_session);

        let v1_res = proxy.get_var_by_name("v1").unwrap();
        assert!(v1_res == "v1");

        let v2_res = proxy.get_var_by_name("v2").unwrap();
        assert!(v2_res == "v2");
    }
}
